Graphs

OpenFisca-UK has a number of built-in features to make visualising reform impacts clear and consistent. Charts for reforms can generally be split into two categories: individual-level (analysing impacts on explicitly defined people, families and households) and population-level (analysing changes to statistics over the entire population). OpenFisca-UK has functions for the following specific plots at these levels, as well as general helper functions:

  • Individual

    • Budget chart

    • MTR chart

  • Population

    • Waterfall chart

    • Distributional chart

      • Quartile

      • Quintile

      • Decile

      • Percentile

All functions are available by importing the graphs module (e.g. from openfisca_uk import graphs).

Additionally, most functions allow the passing in of multiple reforms and their names, to create a graph with a slider to vary between the reforms, using Plotly’s animation frame format. The plots returned are Plotly Figures, so their aesthetics are changeable through built-in Plotly functions.

Individual-level charts

Budget chart

A budget chart is a line chart showing the values of key financial variables as a particular variable changes (by default, employment income).

from openfisca_uk import graphs, reforms, BASELINE_PARAMETERS

increase_UC = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.benefit.universal_credit.standard_allowance.amount.SINGLE_OLD,
    600,
)

graphs.budget_chart(increase_UC, variables=["household_net_income"])

Multiple reforms also work here (from this point on, examples will show multiple reforms).

UC_reforms = [
    reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.benefit.universal_credit.standard_allowance.amount.SINGLE_OLD,
        amount,
    )
    for amount in range(50, 1000, 100)
]
reform_names = [f"UC = £{amount}" for amount in range(50, 1000, 100)]

graphs.budget_chart(
    UC_reforms, names=reform_names, variables=["household_net_income"]
)

MTR chart

MTR charts work in exactly the same way as budget charts.

UC_reforms = [
    reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.benefit.universal_credit.standard_allowance.amount.SINGLE_OLD,
        amount,
    )
    for amount in range(50, 1000, 100)
]
reform_names = [f"UC = £{amount}" for amount in range(50, 1000, 100)]

graphs.mtr_chart(
    UC_reforms, names=reform_names, variables=["household_net_income"]
)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_2205/3569691737.py in <module>
      9 
     10 graphs.mtr_chart(
---> 11     UC_reforms, names=reform_names, variables=["household_net_income"]
     12 )

~/work/openfisca-uk/openfisca-uk/openfisca_uk/graphs/hypothetical.py in mtr_chart(reforms, names, variables, situation_function, invert, derivatives, return_df, **kwargs)
    117         situation_function,
    118         primary_adult_name=primary_adult,
--> 119         include_derivatives=True,
    120     )
    121     for var in df.columns:

~/work/openfisca-uk/openfisca-uk/openfisca_uk/graphs/data.py in get_wide_reform_individual_data(reforms, names, variables, situation_function, varying, vary_min, vary_max, vary_step, include_derivatives, primary_adult_name, **kwargs)
    117             df[f"reform_{variable}"] = reform_sim.calc(variable).sum(axis=0)
    118             if include_derivatives:
--> 119                 df[f"reform_{variable}_deriv"] = reform_sim.calc_deriv(
    120                     variable,
    121                     wrt=varying,

AttributeError: 'IndividualSim' object has no attribute 'calc_deriv'

Population-level charts

Waterfall chart

A waterfall chart shows the cumulative impacts of each top-level component of a reform in terms of overall financial cost.

abolish_PA = reforms.structural.abolish("personal_allowance")
raise_basic_rate = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.tax.income_tax.rates.uk.brackets[0].rate, 0.3
)
increase_CB_eldest = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.benefit.child_benefit.amount.eldest, 80
)
increase_CB_additional = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.benefit.child_benefit.amount.additional, 80
)

multi_part_reform = (
    abolish_PA,
    raise_basic_rate,
    increase_CB_eldest,
    increase_CB_additional,
)

graphs.waterfall_chart(
    multi_part_reform,
    subreform_labels=[
        "Abolish PA",
        "Raise basic rate",
        "Increase CB for eldest",
        "Increase CB for additional",
    ],
)

Note that this decomposition only happens at the top level, so we could control the grouping by editing the structure of the reform, for example to consolidate parts of the reform:

abolish_PA = reforms.structural.abolish("personal_allowance")
raise_basic_rate = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.tax.income_tax.rates.uk.brackets[0].rate, 0.3
)
increase_CB_eldest = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.benefit.child_benefit.amount.eldest, 80
)
increase_CB_additional = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.benefit.child_benefit.amount.additional, 80
)

# Group tax and benefit sides
multi_part_reform = (
    (abolish_PA, raise_basic_rate),
    (increase_CB_eldest, increase_CB_additional),
)

graphs.waterfall_chart(
    multi_part_reform, subreform_labels=["Raise taxes", "Spend on CB"]
)

We can also pass in multiple reforms here, though this does start to become computationally expensive - with \(n\) reforms of \(m\) components each, that’s \(nm + 1\) total microsimulation runs on the microdata.

def create_CB_tax_funded_reform(CB_amount):
    abolish_PA = reforms.structural.abolish("personal_allowance")
    raise_basic_rate = reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.tax.income_tax.rates.uk.brackets[0].rate, 0.3
    )
    increase_CB_eldest = reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.benefit.child_benefit.amount.eldest, CB_amount
    )
    increase_CB_additional = reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.benefit.child_benefit.amount.additional, CB_amount
    )
    multi_part_reform = (
        abolish_PA,
        raise_basic_rate,
        increase_CB_eldest,
        increase_CB_additional,
    )
    return multi_part_reform


CB_reforms = [create_CB_tax_funded_reform(amount) for amount in (80, 100, 120)]
reform_names = [f"CB = £{amount}/week" for amount in (80, 100, 120)]
graphs.waterfall_chart(
    CB_reforms,
    reform_labels=reform_names,
    subreform_labels=[
        "Abolish PA",
        "Raise basic rate",
        "Increase CB for eldest",
        "Increase CB for additional",
    ],
)

Distributional charts

Available for various bucketing sizes, the distributional charts show the impacts on different sections of the population:

from openfisca_uk.api import *

abolish_PA = reforms.structural.abolish("personal_allowance")
raise_basic_rate = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.tax.income_tax.rates.uk.brackets[0].rate, 0.3
)
increase_CB_eldest = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.benefit.child_benefit.amount.eldest, 80
)
increase_CB_additional = reforms.parametric.set_parameter(
    BASELINE_PARAMETERS.benefit.child_benefit.amount.additional, 80
)

# Group tax and benefit sides
multi_part_reform = (
    (abolish_PA, raise_basic_rate),
    (increase_CB_eldest, increase_CB_additional),
)

graphs.decile_chart(
    multi_part_reform, "equiv_household_net_income", "household_net_income"
)

Like other functions, this handles multiple reforms:

from openfisca_uk.api import *


def create_CB_tax_funded_reform(CB_amount):
    abolish_PA = reforms.structural.abolish("personal_allowance")
    raise_basic_rate = reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.tax.income_tax.rates.uk.brackets[0].rate, 0.3
    )
    increase_CB_eldest = reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.benefit.child_benefit.amount.eldest, CB_amount
    )
    increase_CB_additional = reforms.parametric.set_parameter(
        BASELINE_PARAMETERS.benefit.child_benefit.amount.additional, CB_amount
    )
    multi_part_reform = (
        abolish_PA,
        raise_basic_rate,
        increase_CB_eldest,
        increase_CB_additional,
    )
    return multi_part_reform


CB_reforms = [create_CB_tax_funded_reform(amount) for amount in (80, 100, 120)]
reform_names = [f"CB = £{amount}/week" for amount in (80, 100, 120)]
graphs.percentile_chart(
    CB_reforms,
    reform_names=reform_names,
    bucket_variable="equiv_household_net_income",
    change_variable="household_net_income",
)